package lt.liepasoft.locr;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;

import lt.liepasoft.locr.data.Bounds;
import lt.liepasoft.locr.data.OcrImage;
import lt.liepasoft.locr.data.RgbImage;
import lt.liepasoft.locr.data.SymbolImage;

public class ImageManager {
            
    public static OcrImage createOcrImage(RgbImage rgbImg, boolean normalize){                       
        if (rgbImg.npix == 0){return null;}        
        byte[] pixels = new byte[rgbImg.npix];
        
        if (!normalize){
            for (int i=0; i<rgbImg.npix; i++) {
                pixels[i] = rgbToGrayScale(rgbImg.getPixel(i));
            }
        } else {
            byte pix;            
            pixels[0] = pix = rgbToGrayScale(rgbImg.getPixel(0));
            int min = pix, max = pix;
            for (int i = 1; i < rgbImg.npix; i++) {
                pixels[i] = pix = rgbToGrayScale(rgbImg.getPixel(i));
                min = Math.min(min, pix);
                max = Math.max(max, pix);
            }
            int range = max - min;
            if (range < 1) {
                for (int i = 0; i < rgbImg.npix; i++) {
                    pixels[i] = 127;
                }
            } else {
                for (int i = 0; i < rgbImg.npix; i++) {                    
                    pixels[i] =
                            (byte)(Math.min(255,
                                    Math.max(
                                        0,
                                        ((pixels[i]
                                        - min) * 255) / range)
                            )-128);                                        
                }
            }
        }                        
        return new OcrImage(pixels, rgbImg.width, rgbImg.height);
    }
        
    public static RgbImage createRgbImage(OcrImage ocrImg, int threshold, boolean invert){
        if (ocrImg.npix == 0){return null;}        
        int[] pixels = new int[ocrImg.npix];    
        
        if (threshold < -128){
            if (!invert){
                for (int i=0; i<ocrImg.npix; i++){
                    pixels[i] = grayscaleToRgb(ocrImg.getPixel(i));
                }        
            } else {
                for (int i=0; i<ocrImg.npix; i++){
                    pixels[i] = grayscaleToRgb(invert(ocrImg.getPixel(i)));
                }        
            }
        }else{                        
            int black, white;
            if (!invert){
                black = grayscaleToRgb((byte)-128);
                white = grayscaleToRgb((byte)127);
            } else {
                black = grayscaleToRgb((byte)127);
                white = grayscaleToRgb((byte)-128);                
            }
            for (int i=0; i<ocrImg.npix; i++){
                if (ocrImg.getPixel(i) < threshold)
                    pixels[i] = black;
                else
                    pixels[i] = white;
            }
        }
        return new RgbImage(pixels, ocrImg.width, ocrImg.height) ;
    }
    
    public static RgbImage createRgbImage(SymbolImage symbolImg){    
        return createRgbImage(symbolImg, grayscaleToRgb((byte)-128));
//        if (symbolImg.npix == 0){return null;}        
//        int[] pixels = new int[symbolImg.npix];   
//        int 
//            black = grayscaleToRgb((byte)-128),
//            white = grayscaleToRgb((byte)127);
//        
//        int blackSk=0, whiteSk=0;
//        
//        for (int i=0; i<symbolImg.npix; i++){
//            if (symbolImg.pixels.get(i)){
//                //System.out.println("black");
//                pixels[i] = black;
//                blackSk++;
//            } else {
//                //System.out.println("white");
//                pixels[i] = white;
//                whiteSk++;
//            }
//        }                
//        return new RgbImage(pixels, symbolImg.width, symbolImg.height) ;        
    }
    
    public static RgbImage createRgbImage(SymbolImage symbolImg, int color){   
        if (symbolImg.npix == 0){return null;}        
        int[] pixels = new int[symbolImg.npix];   
        int 
            black = color,//grayscaleToRgb((byte)-128),
            white = grayscaleToRgb((byte)127);
        
//        int blackSk=0, whiteSk=0;
        
        for (int i=0; i<symbolImg.npix; i++){
            if (symbolImg.getPixel(i)){
                //System.out.println("black");
                pixels[i] = black;
//                blackSk++;
            } else {
                //System.out.println("white");
                pixels[i] = white;
//                whiteSk++;
            }
        }                
        return new RgbImage(pixels, symbolImg.width, symbolImg.height) ;              
    }
    
    public static SymbolImage createSymbolImage(OcrImage ocrImg, int threshold, boolean invert){
        SymbolImage symbolImage = new SymbolImage(ocrImg.width, ocrImg.height);
        //BitSet pixels = new BitSet(ocrImg.npix);
        boolean black, white;
        if (!invert){
            black = false;
            white = true;
        } else {
            black = true;
            white = false;                
        }
        
        for (int i=0; i<ocrImg.npix; i++){
            if (ocrImg.getPixel(i) < threshold){   
            	symbolImage.setPixel(white, i);
            }else{
            	symbolImage.setPixel(black, i);
            }
        }
                 
        return symbolImage;
    }
    
//    public static SymbolImage createSymbolImage(OcrImage ocrImg, int threshold, boolean invert, Bounds b){
//        SymbolImage symbolImage = new SymbolImage(b.width, b.height);
//           
//        //System.out.println("treasure+ "+threshold);
//        int id=0;        
//        boolean black, white;
//        if (!invert){
//            black = false;
//            white = true;
//        } else {
//            black = true;
//            white = false;                
//        }
//        int blackPixels=0;
//        
//        for (int y=0; y<b.height; y++){
//            for (int x=0; x<b.width; x++) { 
//                if (ocrImg.getPixel(b.x1+x, b.y1+y) < threshold){    
//                    blackPixels++;
//                    symbolImage.pixels.set(id, white);
//                }else{
//                    
//                    symbolImage.pixels.set(id, black);
//                }
//                id++;
//            }
//        }
//        
//        symbolImage.setBlackPixels(blackPixels);        
//        return symbolImage;
//    }
    
//    The way to convert an RGB value to grayscale is to use the following formula: brightness = 0.212671 * R + 0.715160 * G + 0.072169 * B. Assign brightness to the Red, Green, and Blue components of the a new RGB value.
    private static byte rgbToGrayScale(int pix) {
        int 
            r = getRedColor(pix),
            g = getGreenColor(pix),
            b = getBlueColor(pix),
            Y = (int)(0.212671 * r + 0.715160 * g + 0.072169 * b);
            //Y = ((r * 306) + (g * 601) + (b * 117)) >> 10;        
        byte gs = 0;
        if (Y < 0)
        {
            gs = -128;
        }
        else if (Y > 255)
        {
            gs = 127;
        } else {
            gs = (byte)(Y-128);
        }
                
        return gs;
        //return new Color(Y, Y, Y).getRGB();
    }
        
    private static int grayscaleToRgb(byte pix){
        int px = pix+128;
        return ((px&0x0ff)<<16)|((px&0x0ff)<<8)|(px&0x0ff);
    }
    
    private static int getRedColor(int pix){
        return (pix >> 16) & 0xff;
    }
    
    private static int getGreenColor(int pix){
        return (pix >> 8) & 0xff;
    }
    
    private static int getBlueColor(int pix){
        return pix & 0xff;
    }
    
    private static byte invert(byte pix){
        return (byte)~pix;
    }

//    public static SymbolImage interpolateImage(SymbolImage symbolImage){
//        SymbolImage newSymbolImage = new SymbolImage(symbolImage.width, symbolImage.height);
//        int pixelIndex;
//        int blackPixels=0;
//        for (int x=0; x<symbolImage.width; x++) {
//            for (int y=0; y<symbolImage.height; y++){
//                if (symbolImage.getPixel(x, y)){                    
//                    newSymbolImage.setPixel(true, x, y);
//                    for (int sk: new int[]{-1,1}){
//                        pixelIndex = newSymbolImage.getPixelIndex(x+sk, y);
//                        if (0 <= pixelIndex && pixelIndex < newSymbolImage.npix)
//                            newSymbolImage.pixels.set(pixelIndex);
//                        pixelIndex = newSymbolImage.getPixelIndex(x, y+sk);
//                        if (0 <= pixelIndex && pixelIndex < newSymbolImage.npix)
//                            newSymbolImage.pixels.set(pixelIndex);
//                    }
//                }
//            }            
//        }
//        for (int i=0; i<newSymbolImage.npix; i++){
//            if (newSymbolImage.pixels.get(i))
//                blackPixels++;
//        }
//        newSymbolImage.setBlackPixels(blackPixels);
//        return newSymbolImage;
//    }
    
    /**
     * Methods which crops and scale OcrImage
     * @param img Source Image
     * @param b Bounds of where to crop
     * @param targetWidth the width of scaled image
     * @param targetHeight the height of scaled image
     * @return 
     */
//    public static OcrImage cropAndScale(OcrImage img, Bounds b, int targetWidth, int targetHeight, int threshold, boolean invert){
//        ImageUtils utils = ImageUtils.getInstance();
//        
//        OcrImage ocrImg;
//        try {
//            ocrImg = createOcrImage(utils.getImage(utils.scaleImage(utils.rgbImageToImage(createRgbImage(crop(b, img), threshold, invert)), targetWidth, targetHeight, false)), true);
//        } catch(Exception e){
//            System.out.println("crop&scale exception");
//            ocrImg = new OcrImage(new byte[targetWidth*targetHeight], targetWidth, targetHeight);
//        }
//        return ocrImg; 
////        int 
////            id=0,
////            intPartX,
////            fractPartX,
////            intPartY,
////            fractPartY;
////        
////        byte[] pixels = new byte[targetWidth*targetHeight];
////        for (int y=0; y<b.height; y++){
////            for (int x=0; x<b.width; x++) {            
////                pixels[id]=img.getPixel(b.x1+x, b.y1+y);
////                id++;
////            }
////        }
////        
////        return new OcrImage(pixels, targetWidth, targetHeight);
//        
//        
//    }
    
    public static OcrImage crop(Bounds b, OcrImage img){
        int id=0;
        byte[] pixels = new byte[b.height*b.width];
        for (int y=0; y<b.height; y++){
            for (int x=0; x<b.width; x++) {            
                pixels[id]=img.getPixel(b.x1+x, b.y1+y);
                id++;
            }
        }
        OcrImage nImg = new OcrImage(pixels, b.width, b.height);        
        return nImg;        
    }
    
//    public static OcrImage clone(OcrImage img){
//        byte[] pixels = new byte[img.npix];
//        for (int i=0; i<img.npix; i++)
//            pixels[i]=img.pixels[i];
//        
//        OcrImage nImg = new OcrImage(pixels, img.width, img.height);
//        return nImg;
//    }

    public static int[] resizePixels(int[] pixels,int w1,int h1,int w2,int h2) {
        int[] temp = new int[w2*h2] ;        
        int x_ratio = (int)((w1<<16)/w2) +1;
        int y_ratio = (int)((h1<<16)/h2) +1;
        int x2, y2 ;
        for (int i=0;i<h2;i++) {
            for (int j=0;j<w2;j++) {
                x2 = ((j*x_ratio)>>16) ;
                y2 = ((i*y_ratio)>>16) ;
                temp[(i*w2)+j] = pixels[(y2*w1)+x2] ;
            }                
        }                
        return temp ;
    }    
    
    public static RgbImage scale(RgbImage image, int width, int height, boolean aspectRatio){
        int w1 = image.width, h1 = image.height;
        if (aspectRatio){
            if (w1 > h1){
                height = (int)((double)h1*(double)width/(double)w1);
            } else {
                width = (int)((double)w1*(double)height/(double)h1);
            }
        }
        
        int[] temp = new int[width*height] ;
        
        // EDIT: added +1 to account for an early rounding problem
        int x_ratio = (int)((w1<<16)/width) +1;
        int y_ratio = (int)((h1<<16)/height) +1;
        //int x_ratio = (int)((w1<<16)/w2) ;
        //int y_ratio = (int)((h1<<16)/h2) ;
        int x2, y2 ;
        for (int i=0;i<height;i++) {
            for (int j=0;j<width;j++) {
                x2 = ((j*x_ratio)>>16) ;
                y2 = ((i*y_ratio)>>16) ;
                temp[(i*width)+j] = image.getPixel((y2*w1)+x2) ;
            }                
        }                        
        return new RgbImage(temp, width, height);
    }
    
    public static SymbolImage scale(SymbolImage image, int width, int height){
        int 
                w1 = image.width, 
                h1 = image.height;
        
        
        
        SymbolImage scaleImage = new SymbolImage(width, height);
    
        int x_ratio = (int)((w1<<16)/width) +1;
        int y_ratio = (int)((h1<<16)/height) +1;
        //int x_ratio = (int)((w1<<16)/w2) ;
        //int y_ratio = (int)((h1<<16)/h2) ;
        int x2, y2 ;
        for (int i=0;i<height;i++) {
            for (int j=0;j<width;j++) {
                x2 = ((j*x_ratio)>>16) ;
                y2 = ((i*y_ratio)>>16) ;                
                scaleImage.setPixel(image.getPixel((y2*w1)+x2), (i*width)+j);
            }                
        }                   

        return scaleImage;
    }
    
    public static OcrImage scale(OcrImage image, int width, int height) {
        int 
            imgWidth = image.getWidth(),
            imgHeight = image.getHeight();
        
        double 
            scaleAmountX=width/imgWidth, 
            scaleAmountY = height/imgHeight;
        
        System.out.println(scaleAmountX+" "+scaleAmountY);
        
        byte[] pixels = new byte[width*height];        
        OcrImage cropImage = new OcrImage(pixels, width, height);
        
        return cropImage;
        //throw new UnsupportedOperationException("Not yet implemented");
    }
    
    public static boolean isWhitePixel(byte pixel, int threshold, boolean invert){
        return (!invert) ? pixel >= threshold : pixel < threshold;        
    }       
      
}